package com.ybox.common.security.interceptor;

import com.ybox.common.core.web.domain.BaseEntity;
import com.ybox.common.security.utils.SecurityUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Date;
import java.util.Properties;

/**
 * 自定义MyBatis拦截器
 * ①给继承了BaseEntity类 自动添加CreateBy CreateTime UpdateBy UpdateTime 字段
 * ②注意配置resources中的mybatis-config.xml文件中的plugin
 * ③在application.yml文件中加入mybatis配置文件设置  一定将mybatis-config中的对象注入到Sprint容器中，否则不会生效。
 */
@Component("ibatisAuditDataInterceptor")
@Intercepts({@Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class})})
public class IbatisAuditDataInterceptor implements Interceptor {

    private Logger log = LoggerFactory.getLogger(IbatisAuditDataInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 从上下文中获取用户名
        String userName = SecurityUtils.getUsername();

        Object[] args = invocation.getArgs();
        SqlCommandType sqlCommandType = null;

        for (Object object : args) {
            // 从MappedStatement参数中获取到操作类型
            if (object instanceof MappedStatement) {
                MappedStatement ms = (MappedStatement) object;
                sqlCommandType = ms.getSqlCommandType();
                log.debug("操作类型： {}", sqlCommandType);
                continue;
            }
            // 判断参数是否是BaseDO类型
            // 一个参数
            if (object instanceof BaseEntity) {
                BaseEntity entity = (BaseEntity) object;
                if (SqlCommandType.INSERT == sqlCommandType) {
                    Date insertTime = new Date();
                    entity.setCreateBy(userName);
                    entity.setCreateTime(insertTime);
                    continue;
                }
                if (SqlCommandType.UPDATE == sqlCommandType) {
                    Date updateTime = new Date();
                    entity.setUpdateBy(userName);
                    entity.setUpdateTime(updateTime);
                    continue;
                }
            }
            // 兼容MyBatis的updateByExampleSelective(record, example);
            if (object instanceof MapperMethod.ParamMap) {
                log.debug("mybatis arg: {}", object);
                @SuppressWarnings("unchecked")
                MapperMethod.ParamMap<Object> parasMap = (MapperMethod.ParamMap<Object>) object;
                String key = "record";
                if (!parasMap.containsKey(key)) {
                    continue;
                }
                Object paraObject = parasMap.get(key);
                if (paraObject instanceof BaseEntity) {
                    BaseEntity entity = (BaseEntity) object;
                    if (SqlCommandType.UPDATE == sqlCommandType) {
                        Date updateTime = new Date();
                        entity.setUpdateBy(userName);
                        entity.setUpdateTime(updateTime);
                        continue;
                    }
                }
            }
            // 兼容批量插入
            if (object instanceof DefaultSqlSession.StrictMap) {
                log.debug("mybatis arg: {}", object);
                @SuppressWarnings("unchecked")
                DefaultSqlSession.StrictMap<ArrayList<Object>> map = (DefaultSqlSession.StrictMap<ArrayList<Object>>) object;
                String key = "collection";
                if (!map.containsKey(key)) {
                    continue;
                }
                ArrayList<Object> objs = map.get(key);
                for (Object obj : objs) {
                    if (obj instanceof BaseEntity) {
                        BaseEntity entity = (BaseEntity) object;
                        if (SqlCommandType.INSERT == sqlCommandType) {
                            Date insertTime = new Date();
                            entity.setCreateBy(userName);
                            entity.setCreateTime(insertTime);
                        }
                        if (SqlCommandType.UPDATE == sqlCommandType) {
                            Date updateTime = new Date();
                            entity.setUpdateBy(userName);
                            entity.setUpdateTime(updateTime);
                        }
                    }
                }
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {}
}